package com.inexas.cu.rebind;

import java.io.*;
import java.util.*;
import com.google.gwt.core.ext.*;
import com.google.gwt.user.rebind.*;

public class DeferredBindingJavaSource implements SourceWriter {
	private final PrintWriter printWriter;
	private final boolean alreadyWritten;
	private final ClassSourceFileComposerFactory composer;
	private final List<String> interfaces = new ArrayList<String>();
	private final List<String> imports = new ArrayList<String>();
	private final GeneratorContext context;
	private final String packageName, className;
	private SourceWriter sourceWriter;
	private StringBuilder sb;
	private boolean inComment;
	private int indent;
	private boolean inLine;
	private String superclass;

	public DeferredBindingJavaSource( //
	        TreeLogger logger, GeneratorContext context, //
	        String packageName, String outputClassName) {
		assert packageName != null && packageName.length() > 0;
		assert outputClassName != null && outputClassName.length() > 0;

		this.context = context;
		this.packageName = packageName;
		this.className = outputClassName;
		
		printWriter = context.tryCreate(logger, packageName, outputClassName);

		if(printWriter == null) {
			alreadyWritten = true;
		} else {
			alreadyWritten = false;
		}

		composer = new ClassSourceFileComposerFactory(packageName, outputClassName);
	}

	public void setSuperclass(Class<?> superclass) {
		assert sourceWriter == null : "Can't call this after a print, indent, outdent call";
		this.superclass = superclass.getName();
	}

	public void setSuperclass(String className) {
		assert sourceWriter == null : "Can't call this after a print, indent, outdent call";
		try {
			Class.forName(className);
		} catch(final ClassNotFoundException e) {
			assert false : "Can't find class: " + className;
		}
		composer.setSuperclass(className);
	}

	public boolean isAlreadyWritten() {
		return alreadyWritten;
	}

	/**
	 * This is called before using any method that will write output
	 */
	public void printPrelude() {
		// Only the first time...
		if(sb == null) {
			sb = new StringBuilder();
			
			sourceWriter = composer.createSourceWriter(context, printWriter);
			
			sbWriteLine("package " + composer.getCreatedPackage() + ';');
			sbWriteLine("");

			// Imports
			if(imports.size() > 0) {
				for(final String importName : imports) {
					sbWriteLine("import " + importName + ';');
				}
				sbWriteLine("");
			}
			
			// Start the class...
			sbWrite("public class " + composer.getCreatedClassShortName() + ' ');
			
			// Superclass...
			if(superclass != null) {
				sbWrite("extends " + superclass + ' ');
			}

			// Interfaces...
			if(interfaces.size() > 0) {
				sbWrite("implements ");
				boolean first = true;
				for(final String interfaceName : interfaces) {
					if(first) {
						first = false;
					} else {
						sbWrite(", ");
					}
					sbWrite(interfaceName);
				}
				sbWrite(" ");
			}
			sbWriteLine("{");
			indent();
		}
	}

	public void commit(TreeLogger logger) {
		context.commit(logger, printWriter);
	}

	public void beginJavaDocComment() {
		printPrelude();
		sbWriteLine("/**");
		inComment = true;
		sourceWriter.beginJavaDocComment();
	}

	public void endJavaDocComment() {
		assert sb != null : "Can't start with an end Javadoc comment";

		sbWriteLine("*/");
		inComment = false;
		sourceWriter.endJavaDocComment();
	}

	public void indent() {
		printPrelude();
		indent++;
		sourceWriter.indent();
	}

	public void outdent() {
		printPrelude();
		indent--;
		sourceWriter.outdent();
	}

	public void print(String s) {
		printPrelude();
		sbWrite(s);
		sourceWriter.print(s);
	}

	public void println() {
		printPrelude();
		sbWriteLine("");
		sourceWriter.println("");
	}

	public void println(String s) {
		printPrelude();
		sbWriteLine(s);
		sourceWriter.println(s);
	}

	public void indentln(String string) {
		printPrelude();
		indent++;
		sbWriteLine(string);
		indent--;
		sourceWriter.indentln(string);
	}

	private void sbWrite(String string) {
		if(!inLine) {
			for(int i = 0; i < indent; i++) {
				sb.append('\t');
			}
			if(inComment) {
				sb.append(" * ");
			}
		}
		sb.append(string);
	}

	private void sbWriteLine(String string) {
		sbWrite(string);
		sb.append('\n');
		inLine = false;
	}

	public void addImplementedInterface(Class<?> interfaceClass) {
		assert interfaceClass != null : "Null class";
		addImplementedInterface(interfaceClass.getName());
	}

	public void addImplementedInterface(String interfaceName) {
		try {
			final String trimmed = interfaceName.trim();
			final Class<?> interfaceClass = Class.forName(trimmed);
			if(!interfaceClass.isInterface()) {
				throw new RuntimeException("Not an interface: " + trimmed);
			}

			if(interfaces.contains(trimmed)) {
				throw new RuntimeException("Duplicate interface: " + trimmed);
			}

			interfaces.add(trimmed);
			
			composer.addImplementedInterface(interfaceName);
		} catch(final ClassNotFoundException e) {
			throw new RuntimeException("No such interface: " + interfaceName);
		}
	}

	public void addImport(Class<?> type) {
		assert type != null : "Null import type";
		addImport(type.getName());
	}

	public void addImport(String typeName) {
		imports.add(typeName.trim());
		composer.addImport(typeName);
	}

	@Override
    public String toString() {
		return "File: " + packageName + '.' + className + ".java\n" + (sb == null ? "<null>" : sb.toString());
    }

	public void endClass() {
		outdent();
		println("}");
    }

	public String getFullyQualifiedName() {
		return packageName + '.' + className;
    }

}
